﻿// ------------------------------------------------------------------------
// 项目名称：Canroc.Net 
// 版权归属：Canroc（https://gitee.com/canroc）
//
// 许可证信息
// Canroc.Net项目主要遵循 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-APACHE 文件。
//
// 使用条款：
// 1.使用本项目应遵守相关法律法规和许可证的要求。
// 2.不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动。
// 3.任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任
//
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
//
// 其他重要信息
// Canroc.Net 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// ------------------------------------------------------------------------

using System.Text.RegularExpressions;

namespace Canroc.Net.Web.Core.Middleware;

/// <summary>
///     上传的文件检查中间件
/// </summary>
public partial class UploadCheckMiddleware(RequestDelegate next)
{
    [GeneratedRegex("[*×――^$%~!＠@＃#$…&%￥—+=<>《》!！??？:：•`·、。，；,.;/\'\"{}‘’“”-]")]
    public static partial Regex SpecialCharRegex();

    /// <summary>
    ///     中间件执行方法
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            // 判断请求的类型是否为表单形式
            if (!context.Request.HasFormContentType)
            {
                // 执行下一个中间件
                await next(context);
                return;
            }

            // 判断是否为上传文件请求
            var form = await context.Request.ReadFormAsync();
            if (!form.Files.Any(f => f.Length > 0))
            {
                await next(context);
                return;
            }

            // 获取上传文件选项信息
            var uploadOption = App.GetOptions<UploadOption>();
            // 遍历文件，检测
            foreach (var file in form.Files)
            {
                var fileName = Path.GetFileNameWithoutExtension(file.FileName);
                var extension = Path.GetExtension(file.FileName);
                // 判断文件名是否为空
                if (fileName.IsNullOrEmpty())
                {
                    context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                    await context.Response.WriteAsJsonAsync(
                        RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError, "文件名不可为空 无法上传！"));
                    return;
                }

                // 正则表达式判断文件名中是否有特殊字符
                if (SpecialCharRegex().IsMatch(fileName))
                {
                    context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                    await context.Response.WriteAsJsonAsync(
                        RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError,
                            $"[{fileName}] 中出现特殊字符 无法上传！"));
                    return;
                }

                // 判断文件大小
                var fileSize = file.Length / 1024;
                if (fileSize > uploadOption.MaxSize)
                {
                    context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                    await context.Response.WriteAsJsonAsync(RestfulResultHelper.ErrorMessage(
                        HttpStatusCodeEnum.InternalServerError,
                        $"[{fileName}] 大于{(int)(uploadOption.MaxSize / 1024)}M 无法上传！"));
                    return;
                }

                // 判断文件类型可传
                var fileExtenstionRule =
                    uploadOption.AllowExtensions.FirstOrDefault(t => t.Name == extension);
                if (fileExtenstionRule is null)
                {
                    context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                    await context.Response.WriteAsJsonAsync(
                        RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError,
                            $"[{fileName}] 不在可上传类型中 无法上传！"));
                    return;
                }

                // 判断是否为篡改的格式
                var headerBuffer = new byte[fileExtenstionRule.BitCount];
                await using var fileStream = file.OpenReadStream();
                if (fileStream.Length < fileExtenstionRule.BitCount)
                {
                    context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                    await context.Response.WriteAsJsonAsync(
                        RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError,
                            $"[{fileName}] 文件大小不正确 无法上传！"));
                    return;
                }

                _ = await fileStream.ReadAsync(headerBuffer);
                var header = ByteArrayHelper.ByteToHexStr(headerBuffer);
                var qualified = fileExtenstionRule.MatchingMethod == "StartsWith"
                    ? header.StartsWith(fileExtenstionRule.HexHeader)
                    : header.EndsWith(fileExtenstionRule.HexHeader);
                if (qualified) continue;

                context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
                await context.Response.WriteAsJsonAsync(
                    RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError,
                        $"[{fileName}] 非法文件 无法上传！"));
                return;
            }

            await next(context);
        }
        catch (Exception ex)
        {
            context.Response.StatusCode = (int)HttpStatusCodeEnum.InternalServerError;
            await context.Response.WriteAsJsonAsync(
                RestfulResultHelper.ErrorMessage(HttpStatusCodeEnum.InternalServerError, $"文件上传异常：{ex.Message}"));
        }
    }
}